import { BiConsumer, Predicate, Supplier } from '../Function'
import { KeyValue } from '../model/KeyValue'

/**
 * ObjectTool
 * @author 冰凝
 * @date 2022-09-19 09:42:58
 **/
export class ObjectTool {

    /**
     * 判断入参是否是空
     * @param {Object} o 待检查对象
     * @return {boolean} 如果 o 是 null 或者 undefined 返回 true, 否则 返回 false
     */
    public static isNull(o: any): boolean {
        return o === null || o === undefined
    }

    /**
     * 对象非空
     * @param o
     * @return {boolean}
     */
    public static isNotNull(o: any): boolean {
        return o !== null && o !== undefined
    }

    /**
     * 对象是空对象: {}
     * @param o 待检查对象
     * @return {boolean} 是空?
     */
    public static isEmpty(o: any): boolean {
        return this.isNull(o) || Object.keys(o).length <= 0
    }

    /**
     * 对象非空
     */
    public static isNotEmpty(o: any): boolean {
        return !this.isEmpty(o)
    }

    /**
     * 要求参数非空, 否则执行自定义回调
     */
    public static requireNotNullElse(o: Record<string, any>, callback: () => void = this.throwNPE): void {
        if (this.isNull(o)) {
            this.requireNotNullElse(callback)
            callback()
        }
    }

    public static requireNotNull<T>(o: T): T {
        if (this.isNull(o)) {
            this.throwNPE()
        }
        return o
    }

    /**
     * 要求非空, 否则返回默认值
     * @param o 检查对象
     * @param defaultValue 默认值, 非空
     */
    public static requireNotNullDefault<T>(o: T | undefined | null, defaultValue: T): T {
        return this.isNotNull(o) ? o as T : this.requireNotNull<T>(defaultValue)
    }

    /**
     * 要求非空, 否则执行给定 {@link Supplier} 并返回
     * @param o 检查对象
     * @param supplier 默认值提供
     */
    public static requireNotNullSupplier<T>(o: T, supplier: Supplier<T>): T {
        return this.isNotNull(o) ? o : supplier()
    }

    /**
     * 对象"复制", 使用 Object.keys 复制第一层属性的引用
     * @param source 源
     * @param target 目标
     * @param ignoreKeys 忽略的属性
     */
    public static copyByKeys<S = Record<string, any>, T = Record<string, any>>(source: S, target: T, ...ignoreKeys: string[]): void {
        this.requireNotNull(source)
        this.requireNotNull(target)

        const ignore = this.requireNotNullDefault(ignoreKeys, [])
        // 忽略下划线
        // @ts-ignore
        for (const key of Object.keys(source)) {
            if (ignore.some(i => i === key)) {
                continue
            }
            // @ts-ignore
            target[key] = source[key]
        }
    }

    /**
     * 对象键值反转, 不修改原对象
     * @param o
     */
    public static keyValueReverse<K extends PropertyKey, V extends PropertyKey>(o: Record<K, V>): Record<V, K> {
        const res = {} as Record<V, K>
        if (this.isEmpty(o)) {
            return res
        }
        for (let key of Object.keys(o)) {
            // @ts-ignore
            res[o[key]] = key
        }
        return res
    }

    /**
     * 根据指定key匹配谓词, 删除一部分属性;
     * 修改源对象
     * @param o 待处理对象
     * @param pre 入参key ,返回 true 删除这个属性
     */
    public static deleteByKey<K extends PropertyKey, V = any>(o: Record<K, V>, pre: Predicate<K>): void {
        if (this.isEmpty(o)) {
            return
        }
        this.requireNotNull(pre)
        for (const key of Object.keys(o)) {
            if (pre(key as K)) {
                delete o[key as K]
            }
        }
    }

    /**
     * 根据指定value匹配谓词, 删除一部分属性;
     * 修改源对象
     */
    public static deleteByValue<K extends PropertyKey, V = any>(o: Record<K, V>, pre: Predicate<V>): void {
        if (this.isEmpty(o)) {
            return
        }
        this.requireNotNull(pre)
        for (let key of Object.keys(o)) {
            if (pre(o[key as K])) {
                delete o[key as K]
            }
        }
    }

    /**
     * 根据指定 key | value 匹配谓词, 删除一部分属性;
     * 修改源对象
     */
    public static deleteProperty<K extends PropertyKey, V = any>(o: Record<K, V>, keyPre: Predicate<K>, valPre: Predicate<V>): void {
        if (this.isEmpty(o)) {
            return
        }
        this.requireNotNull(keyPre)
        this.requireNotNull(valPre)
        for (const key of Object.keys(o)) {
            if (keyPre(key as K) || valPre(o[key as K])) {
                delete o[key as K]
            }
        }
    }

    /**
     * 过滤空属性, 不修改源对象
     * 空定义: {@link #isNull}
     * @param {object} o 允许为空
     * @return 永远非空
     */
    public static filterNullProperty<T>(o: T | null | undefined): T {
        if (this.isNull(o)) {
            return {} as T
        }
        const res = this.copy<T>(o as T)
        // @ts-ignore
        this.deleteByValue(res, arg => this.isNull(arg))
        return res
    }

    /**
     * 对象属性和值遍历
     * 真 - 把对象当 Map 用;
     * 回调中传递的是原对象属性和值, 这意味着对其进行任何操作都反馈到源对象 (如果可能, 一般所有引用类型都是如此)
     * @param o 待遍历对象, 允许为空 (不执行任何操作)
     * @param bc 对象中每一组 key: value 的消费者; 第一个参数为属性名; 第二个参数为属性值
     * @exception Error NPE 如果 bc 为空
     * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
     */
    public static forEach<K extends PropertyKey, V>(o: Record<K, V>, bc: BiConsumer<string, V>) {
        if (this.isNull(bc)) {
            this.throwNPE()
        }
        if (this.isNull(o)) {
            return
        }
        const keys = Object.keys(o)
        if (keys.length <= 0) {
            return
        }
        // @ts-ignore
        keys.forEach(key => bc(key, o[key]))
    }

    /**
     * 对象转 {@link KeyValue} 数组
     * @param o
     */
    public static toArray<T, K extends keyof T>(o: T): Array<KeyValue<K, T[K]>> {
        if (this.isEmpty(o)) {
            return []
        }
        // noinspection TypeScriptValidateTypes
        // @ts-ignore
        return Object.keys(o).map(key => new KeyValue<K, T[K]>(key, o[key]))
    }

    /**
     * 对象值相等比较
     * <li>如果 a b 引用相等  返回 true</li>
     * <li>如果 a b 全 Null    返回 true</li>
     * <li>如果 a b 含任意一个 Null (另外一个不是 Null) 返回 false</li>
     * <li>最后 根据 a b 转为 JSON 后的字符串判断相等性</li>
     *
     * 注意: 因为 {@code JSON.stringify} 的问题; 不要传入二进制对象比较, 结果未知
     * @param a 待判断值 允许为空
     * @param b 待判断值 允许为空
     * @return {boolean} a b 值相等
     */
    public static equals(a: any, b: any) {
        if (a === b) {
            return true
        }
        let aIsNull = this.isNull(a)
        let bIsNull = this.isNull(b)
        // 都是 Null
        if (aIsNull && aIsNull === bIsNull) {
            return true
        }
        // 排除了全 Null; 这里 应该是: true || false ; false || true
        if (aIsNull || bIsNull) {
            return false
        }
        let aJson = JSON.stringify(a)
        let bJson = JSON.stringify(b)
        if (aJson.length !== bJson.length) {
            return false
        }
        return aJson == bJson
    }

    /**
     * 复制, 不要对二进制对象使用, 性能差, 结果未知
     * @param o 源
     * @return 新对象
     */
    public static copy<S>(o: S): S {
        return JSON.parse(JSON.stringify(o))
    }

    private static throwNPE() {
        throw new Error('NPE')
    }
}
